/*---------------------------------------------------------*\
| RGBController_CMARGBGen2A1Controller.cpp                  |
|                                                           |
|   Driver for Cooler Master ARGB Gen 2 A1 controller       |
|                                                           |
|   Morgan Guimard (morg)                       26 Jun 2022 |
|                                                           |
|   This file is part of the OpenRGB project                |
|   SPDX-License-Identifier: GPL-2.0-only                   |
\*---------------------------------------------------------*/

#include <chrono>
#include <thread>
#include "RGBController_CMARGBGen2A1Controller.h"

/**------------------------------------------------------------------*\
    @name Coolermaster ARGB A1
    @category LEDStrip
    @type USB
    @save :white_check_mark:
    @direct :white_check_mark:
    @effects :white_check_mark:
    @detectors DetectCoolerMasterARGBGen2A1
    @comment This device does not have Gen 2 support in OpenRGB yet.

    Gen2 has auto-resize feature and parallel to serial magical stuff.<
\*-------------------------------------------------------------------*/

RGBController_CMARGBGen2A1Controller::RGBController_CMARGBGen2A1Controller(CMARGBGen2A1controller* controller_ptr)
{
    controller                  = controller_ptr;
    name                        = "CoolerMaster LED Controller A1";
    vendor                      = "CoolerMaster";
    type                        = DEVICE_TYPE_LEDSTRIP;
    description                 = name;
    location                    = controller->GetDeviceLocation();
    serial                      = controller->GetSerialString();
    version                     = "";

    mode Direct;
    Direct.name                 = "Direct";
    Direct.value                = CM_ARGB_GEN2_A1_DIRECT_MODE;
    Direct.flags                = MODE_FLAG_HAS_PER_LED_COLOR;
    Direct.color_mode           = MODE_COLORS_PER_LED;
    modes.push_back(Direct);

    mode Spectrum;
    Spectrum.name               = "Spectrum";
    Spectrum.value              = CM_ARGB_GEN2_A1_SPECTRUM_MODE;
    Spectrum.flags              = MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_MANUAL_SAVE;
    Spectrum.color_mode         = MODE_COLORS_NONE;
    Spectrum.brightness         = CM_ARGB_GEN2_A1_BRIGHTNESS_MAX;
    Spectrum.brightness_min     = CM_ARGB_GEN2_A1_BRIGHTNESS_MIN;
    Spectrum.brightness_max     = CM_ARGB_GEN2_A1_BRIGHTNESS_MAX;
    Spectrum.speed              = CM_ARGB_GEN2_A1_SPEED_MAX/2;
    Spectrum.speed_min          = CM_ARGB_GEN2_A1_SPEED_MIN;
    Spectrum.speed_max          = CM_ARGB_GEN2_A1_SPEED_MAX;
    modes.push_back(Spectrum);

    mode Static;
    Static.name                 = "Static";
    Static.value                = CM_ARGB_GEN2_A1_STATIC_MODE;
    Static.flags                = MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_MANUAL_SAVE;
    Static.color_mode           = MODE_COLORS_MODE_SPECIFIC;
    Static.brightness           = CM_ARGB_GEN2_A1_BRIGHTNESS_MAX;
    Static.brightness_min       = CM_ARGB_GEN2_A1_BRIGHTNESS_MIN;
    Static.brightness_max       = CM_ARGB_GEN2_A1_BRIGHTNESS_MAX;
    Static.colors.resize(1);
    modes.push_back(Static);

    mode Reload;
    Reload.name                 = "Reload";
    Reload.value                = CM_ARGB_GEN2_A1_RELOAD_MODE;
    Reload.flags                = MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_MANUAL_SAVE;
    Reload.color_mode           = MODE_COLORS_MODE_SPECIFIC;
    Reload.brightness           = CM_ARGB_GEN2_A1_BRIGHTNESS_MAX;
    Reload.brightness_min       = CM_ARGB_GEN2_A1_BRIGHTNESS_MIN;
    Reload.brightness_max       = CM_ARGB_GEN2_A1_BRIGHTNESS_MAX;
    Reload.speed                = CM_ARGB_GEN2_A1_SPEED_MAX/2;
    Reload.speed_min            = CM_ARGB_GEN2_A1_SPEED_MIN;
    Reload.speed_max            = CM_ARGB_GEN2_A1_SPEED_MAX;
    Reload.colors.resize(1);
    modes.push_back(Reload);

    mode Recoil;
    Recoil.name                 = "Recoil";
    Recoil.value                = CM_ARGB_GEN2_A1_RECOIL_MODE;
    Recoil.flags                = MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_MANUAL_SAVE;
    Recoil.color_mode           = MODE_COLORS_MODE_SPECIFIC;
    Recoil.brightness           = CM_ARGB_GEN2_A1_BRIGHTNESS_MAX;
    Recoil.brightness_min       = CM_ARGB_GEN2_A1_BRIGHTNESS_MIN;
    Recoil.brightness_max       = CM_ARGB_GEN2_A1_BRIGHTNESS_MAX;
    Recoil.speed                = CM_ARGB_GEN2_A1_SPEED_MAX/2;
    Recoil.speed_min            = CM_ARGB_GEN2_A1_SPEED_MIN;
    Recoil.speed_max            = CM_ARGB_GEN2_A1_SPEED_MAX;
    Recoil.colors.resize(1);
    modes.push_back(Recoil);

    mode Breathing;
    Breathing.name              = "Breathing";
    Breathing.value             = CM_ARGB_GEN2_A1_BREATHING_MODE;
    Breathing.flags             = MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_MANUAL_SAVE;
    Breathing.color_mode        = MODE_COLORS_MODE_SPECIFIC;
    Breathing.brightness        = CM_ARGB_GEN2_A1_BRIGHTNESS_MAX;
    Breathing.brightness_min    = CM_ARGB_GEN2_A1_BRIGHTNESS_MIN;
    Breathing.brightness_max    = CM_ARGB_GEN2_A1_BRIGHTNESS_MAX;
    Breathing.speed             = CM_ARGB_GEN2_A1_SPEED_MAX/2;
    Breathing.speed_min         = CM_ARGB_GEN2_A1_SPEED_MIN;
    Breathing.speed_max         = CM_ARGB_GEN2_A1_SPEED_MAX;
    Breathing.colors.resize(1);
    modes.push_back(Breathing);


    mode Refill;
    Refill.name                 = "Refill";
    Refill.value                = CM_ARGB_GEN2_A1_REFILL_MODE;
    Refill.flags                = MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_MANUAL_SAVE;
    Refill.color_mode           = MODE_COLORS_MODE_SPECIFIC;
    Refill.brightness           = CM_ARGB_GEN2_A1_BRIGHTNESS_MAX;
    Refill.brightness_min       = CM_ARGB_GEN2_A1_BRIGHTNESS_MIN;
    Refill.brightness_max       = CM_ARGB_GEN2_A1_BRIGHTNESS_MAX;
    Refill.speed                = CM_ARGB_GEN2_A1_SPEED_MAX/2;
    Refill.speed_min            = CM_ARGB_GEN2_A1_SPEED_MIN;
    Refill.speed_max            = CM_ARGB_GEN2_A1_SPEED_MAX;
    Refill.colors.resize(1);
    modes.push_back(Refill);

    mode Demo;
    Demo.name                   = "Demo";
    Demo.value                  = CM_ARGB_GEN2_A1_DEMO_MODE;
    Demo.flags                  = MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE;
    Demo.color_mode             = MODE_COLORS_NONE;
    Demo.brightness             = CM_ARGB_GEN2_A1_BRIGHTNESS_MAX;
    Demo.brightness_min         = CM_ARGB_GEN2_A1_BRIGHTNESS_MIN;
    Demo.brightness_max         = CM_ARGB_GEN2_A1_BRIGHTNESS_MAX;
    modes.push_back(Demo);

    mode FillFlow;
    FillFlow.name               = "Fill Flow";
    FillFlow.value              = CM_ARGB_GEN2_A1_FILL_FLOW_MODE;
    FillFlow.flags              = MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_MANUAL_SAVE;;
    FillFlow.color_mode         = MODE_COLORS_NONE;
    FillFlow.brightness         = CM_ARGB_GEN2_A1_BRIGHTNESS_MAX;
    FillFlow.brightness_min     = CM_ARGB_GEN2_A1_BRIGHTNESS_MIN;
    FillFlow.brightness_max     = CM_ARGB_GEN2_A1_BRIGHTNESS_MAX;
    FillFlow.speed              = CM_ARGB_GEN2_A1_SPEED_MAX/2;
    FillFlow.speed_min          = CM_ARGB_GEN2_A1_SPEED_MIN;
    FillFlow.speed_max          = CM_ARGB_GEN2_A1_SPEED_MAX;
    modes.push_back(FillFlow);

    mode Rainbow;
    Rainbow.name               = "Rainbow";
    Rainbow.value              = CM_ARGB_GEN2_A1_RAINBOW_MODE;
    Rainbow.flags              = MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_MANUAL_SAVE;;
    Rainbow.color_mode         = MODE_COLORS_NONE;
    Rainbow.brightness         = CM_ARGB_GEN2_A1_BRIGHTNESS_MAX;
    Rainbow.brightness_min     = CM_ARGB_GEN2_A1_BRIGHTNESS_MIN;
    Rainbow.brightness_max     = CM_ARGB_GEN2_A1_BRIGHTNESS_MAX;
    Rainbow.speed              = CM_ARGB_GEN2_A1_SPEED_MAX/2;
    Rainbow.speed_min          = CM_ARGB_GEN2_A1_SPEED_MIN;
    Rainbow.speed_max          = CM_ARGB_GEN2_A1_SPEED_MAX;
    modes.push_back(Rainbow);

    mode Custom;
    Custom.name                 = "Custom";
    Custom.value                = CM_ARGB_GEN2_A1_CUSTOM_MODE;
    Custom.flags                = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_MANUAL_SAVE;
    Custom.color_mode           = MODE_COLORS_PER_LED;
    modes.push_back(Custom);

    mode Off;
    Off.name                    = "Off";
    Off.value                   = CM_ARGB_GEN2_A1_OFF_MODE;
    Off.flags                   = MODE_FLAG_MANUAL_SAVE;
    Off.color_mode              = MODE_COLORS_NONE;
    modes.push_back(Off);

    SetupZones();
}

RGBController_CMARGBGen2A1Controller::~RGBController_CMARGBGen2A1Controller()
{
    delete controller;
}

void RGBController_CMARGBGen2A1Controller::SetupZones()
{
    unsigned int total_leds = 0;

    for(unsigned int channel = 0; channel < CM_ARGB_GEN2_A1_CHANNEL_COUNT; channel++)
    {
        zone new_zone;

        new_zone.name       = "Channel " + std::to_string(channel + 1);
        new_zone.type       = ZONE_TYPE_LINEAR;
        new_zone.leds_min   = 0;
        new_zone.leds_max   = CM_ARGB_GEN2_A1_CHANNEL_MAX_SIZE;
        new_zone.leds_count = 0;
        new_zone.matrix_map = nullptr;

        zones.push_back(new_zone);

        total_leds += new_zone.leds_count;
    }

    leds.resize(total_leds);

    for(unsigned int i = 0; i < total_leds; i++)
    {
        leds[i].name = "LED " + std::to_string(i + 1);
    }

    SetupColors();
}

void RGBController_CMARGBGen2A1Controller::ResizeZone(int zone, int new_size)
{
    zones[zone].leds_count = new_size;

    // todo: refactor with above code
    unsigned int total_leds = 0;

    for(unsigned int channel = 0; channel < CM_ARGB_GEN2_A1_CHANNEL_COUNT; channel++)
    {
        total_leds += zones[channel].leds_count;
    }

    leds.resize(total_leds);

    for(unsigned int i = 0; i < total_leds; i++)
    {
        leds[i].name = "LED " + std::to_string(i + 1);
    }

    controller->SetupZoneSize(zone, new_size);

    SetupColors();
}

void RGBController_CMARGBGen2A1Controller::DeviceUpdateLEDs()
{
    for(unsigned int channel = 0; channel < CM_ARGB_GEN2_A1_CHANNEL_COUNT; channel ++)
    {
        UpdateZoneLEDs(channel);
    }
}

void RGBController_CMARGBGen2A1Controller::UpdateZoneLEDs(int zone)
{
    if(zones[zone].leds_count > 0)
    {
        unsigned int start = zones[zone].start_idx;
        unsigned int end = start + zones[zone].leds_count;

        std::vector<RGBColor> zone_colors(colors.begin() + start , colors.begin() + end);

        if(modes[active_mode].value == CM_ARGB_GEN2_A1_DIRECT_MODE)
        {
            controller->SendDirectChannel(zone, zone_colors);
        }
        else
        {
            controller->SetCustomColors(zone, zone_colors);
        }
    }
}

void RGBController_CMARGBGen2A1Controller::UpdateSingleLED(int /*led*/)
{
    DeviceUpdateLEDs();
}

void RGBController_CMARGBGen2A1Controller::SetCustomMode()
{
    active_mode = 0;
}

void RGBController_CMARGBGen2A1Controller::DeviceUpdateMode()
{
    const mode& active = modes[active_mode];

    if(active.value == CM_ARGB_GEN2_A1_DIRECT_MODE)
    {
        controller->SetupDirectMode();
    }
    else
    {
        RGBColor color = active.color_mode == MODE_COLORS_MODE_SPECIFIC ?
                    active.colors[0] : 0;

        controller->SetMode
                (
                    active.value,
                    active.speed,
                    active.brightness,
                    color,
                    active.color_mode == MODE_COLORS_RANDOM
                );
    }
}

void RGBController_CMARGBGen2A1Controller::DeviceSaveMode()
{
    controller->SaveToFlash();
}

